Conversation
|
|
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. |
|
Just to pile on, we are also seeing this issue and this fix seems to address the underlying issue in our use case. |
|
We hit this in production as well (portaled To help get this merged, here is what is currently blocking it and a follow-up that addresses each point:
With the follow-up applied, all four steps of the CircleCI Regression testimport React from 'react';
import { render } from '@testing-library/react';
import Select from '../Select';
import { OPTIONS } from './constants';
// Regression test for https://github.com/JedWatson/react-select/issues/6003
// When the control's bounding rect drifts by a sub-pixel amount between reads
// (zoomed viewport, fractional DPI, scrolling ancestor), MenuPortal must not
// enter a synchronous setState loop ("Maximum update depth exceeded").
test('portaled menu does not loop when the control rect drifts sub-pixel', () => {
const originalGetBoundingClientRect = Element.prototype.getBoundingClientRect;
let reads = 0;
Element.prototype.getBoundingClientRect = function (this: Element) {
if (!this.classList.contains('react-select__control')) {
return originalGetBoundingClientRect.call(this);
}
const top = 100 + (reads++ % 2 ? 0.3 : 0);
return {
x: 0,
y: top,
top,
bottom: top + 38,
left: 0,
right: 200,
width: 200,
height: 38,
toJSON: () => ({}),
};
};
const props = {
classNamePrefix: 'react-select',
options: OPTIONS,
menuPortalTarget: document.body,
onChange: () => {},
onInputChange: () => {},
onMenuOpen: () => {},
onMenuClose: () => {},
inputValue: '',
value: null,
};
try {
const { rerender } = render(<Select {...props} menuIsOpen={false} />);
expect(() => rerender(<Select {...props} menuIsOpen />)).not.toThrow();
expect(
document.body.querySelector('.react-select__menu-portal')
).toBeTruthy();
} finally {
Element.prototype.getBoundingClientRect = originalGetBoundingClientRect;
}
});@9KenM, feel free to pull these into your branch. Alternatively, a maintainer can push them directly, since "Allow edits by maintainers" is on. I'm also happy to open a separate PR on top of yours with you credited as co-author, if that's easier. |
Problem
updateComputedPositioncloses overcomputedPositionand lists itssub-properties (
offset,rect.left,rect.width) asuseCallbackdependencies. This means every call to
setComputedPositionproduces anew callback identity, which invalidates the
useLayoutEffect([updateComputedPosition])and immediately re-fires thecallback.
Under subpixel-drifting conditions (zoomed-out viewport, scrollable
ancestor, or fractional DPI) the measured
offsetoscillates by <1pxbetween frames. Each oscillation triggers
setComputedPosition→identity change → effect re-run → repeat, until React throws:
Fixes #6003.
Solution
Track
computedPositionin a ref so the comparison insideupdateComputedPositionalways reads the latest value without thecallback depending on it. This gives
updateComputedPositiona stableidentity across renders and breaks the loop.
Reproduction
<Select menuPosition="fixed" />inside a scrollable container